import sys
from PySide6.QtWidgets import (
    QApplication,
    QVBoxLayout,
    QHBoxLayout,
    QMainWindow,
    QTabWidget,
    QMdiArea,
    QTableWidget,
    QToolBar,
    QGraphicsView,
    QFileDialog,
    QPushButton,
    QTableWidgetItem,
    QMdiSubWindow,
    QHeaderView,
    QLabel,
    QWidget,
    QLineEdit,
    QMessageBox,
    QProgressBar,
    QTextEdit,
)
from PySide6.QtCore import (
    Qt,
    Slot,
    QSettings,
    QByteArray,
    QFile,
    QThread,
    QObject,
    Signal,
    QPointF,
)
from PySide6.QtGui import QAction, QIcon, QKeySequence, QFont, QColor, QPainter
from PySide6.QtCharts import (
    QChart,
    QChartView,
    QLineSeries,
    QBarCategoryAxis,
    QBarSet,
    QPieSeries,
    QStackedBarSeries,
    QValueAxis,
    QBarSeries,
    QScatterSeries
)
import pandas as pd
import pdfplumber as pp
import cpca
import numpy as np


class Work(QObject):
    finish_signal = Signal(pd.DataFrame)
    progress_doing_signal = Signal(int)
    progress_complete_signal = Signal(int)

    def __init__(self):
        super().__init__()

    def pdf_2_df(self, path):
        """
        PDF文件转DataFrame
        """
        file = pp.open(path)
        first = file.pages[0].extract_table()
        cols = first[2]
        data = pd.DataFrame(first[3:], columns=cols)

        n = len(file.pages)  # 一共有多少页
        self.progress_complete_signal.emit(n - 2)  # 发送给progressbar做进度条用的
        for i in range(1, n):
            page = file.pages[i]
            p = page.extract_table()
            t1 = pd.DataFrame(p, columns=cols)
            data = pd.concat([data, t1], ignore_index=True)
            self.progress_doing_signal.emit(i)  # 发送信号给主程序

        data.replace(
            to_replace={
                "交易时间": {"\n": " "},
                "交易类型": {"\n": ""},
                "收/支/其他": {"\n": ""},
                "交易方式": {"\n": ""},
                "金额(元)": {"\n": ""},
                "交易对方": {"\n": ""},
            },
            inplace=True,
            regex=True,
        )
        data.drop(columns=["交易单号", "商户单号"], inplace=True)
        data["交易时间"] = data["交易时间"].astype("datetime64[ns]")
        data["金额(元)"] = data["金额(元)"].astype("float")

        df = data.copy()
        # 重塑交易方式
        df.replace(
            {
                "交易方式": {
                    ".*银行.*": "银行",
                    "零钱通": "零钱",
                    "/": "零钱",
                    ".*农信.*": "银行",
                    "经营账户": "银行",
                    "分付": "零钱",
                }
            },
            regex=True,
            inplace=True,
        )
        df.loc[
            (df.交易类型.str.contains("提现") & (df.交易对方.str.contains("银行"))),
            "交易方式",
        ] = "银行"

        # 重塑交易类型
        # df[['银行记账','微信记账']] = np.NaN
        df.loc[(df.交易类型.str.contains("还款")), "收/支/其他"] = "支出"
        df.loc[(df.交易类型.str.contains("提现")), "收/支/其他"] = "收入"
        df.loc[(df.交易类型 == "零钱充值"), "收/支/其他"] = "支出"
        df.loc[(df.交易类型.str.contains("银行")), "收/支/其他"] = "支出"
        df.drop(index=(df[df["收/支/其他"] == "其他"]).index, axis=0, inplace=True)
        return df

    @Slot(str)
    def process_pdf(self, path):
        df = self.pdf_2_df(path)
        self.finish_signal.emit(df)


# 主程序
class MainWindow(QMainWindow):
    window_signal = Signal(str)

    def __init__(self):
        super().__init__()
        # ________________UI__________________________________________________________
        widget = QWidget()  # widget的前面没有加上self是因为不需要在外部访问这个变量
        layout = QVBoxLayout(widget)  # 同上，布局选项不应该也不需要被外部访问

        self._progress_bar = QProgressBar()
        layout.addWidget(self._progress_bar)

        self._mdi_area = QMdiArea()
        self._mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        layout.addWidget(self._mdi_area)

        self.read_settings()
        self.create_tool_bars()
        self.create_status_bar()
        self.setWindowTitle("微信流水处理2.0  editor : chenjie")
        self.setCentralWidget(widget)
        # ___________________________________________________________________________

        # ___________________________________Thread_______________________________
        self.main_thread = QThread()
        self.worker = Work()
        # 连接信号到槽
        self.worker.moveToThread(self.main_thread)

        # 将外部选择的文件路径传递给子线程窗口的函数，让其在后台处理
        self.window_signal.connect(self.worker.process_pdf)

        # 将子线程处理好的文件传递给主窗口，方便后续处理
        self.worker.finish_signal.connect(self.get_df)

        # 进度条最大进度传递给MainWindow
        self.worker.progress_complete_signal.connect(self.get_max_v)

        # 进度每处理一次，将i传递给MainWindow
        self.worker.progress_doing_signal.connect(self.update_progressbar)

    def start_thread(self):
        """进程开始"""
        if not self.main_thread.isRunning():
            self.main_thread.start()

    def stop_thread(self):
        """进程终止"""
        self.main_thread.quit()
        self.main_thread.wait()

    # ---------------------------------------------------------------------------

    # --------------------进度条-------------------------------------------------------
    def get_max_v(self, max_v):
        self._progress_bar.setMaximum(max_v)

    def update_progressbar(self, v):
        self._progress_bar.setValue(v)

    # -------------------------------------------------------------------------------

    # ______method_______________________________________________________________

    def create_tool_bars(self):
        """
        创建左侧工具栏
        """
        self._analysis_tool_bar = self.addToolBar("tools")
        self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, self._analysis_tool_bar)
        self._analysis_tool_bar.setFixedWidth(50)

        icon1 = QIcon.fromTheme("document-open")
        self.action1 = QAction(
            icon1, "&Open", self, triggered=self.get_path, statusTip="从外部打开文件"
        )
        # self.action1.triggered.connect(self.start_thread)
        self._analysis_tool_bar.addAction(self.action1)

        icon1_1 = QIcon.fromTheme("document-open")
        self.action1_1 = QAction(
            icon1_1,
            "&Open",
            self,
            triggered=self.analysis_income_expend_tab,
            statusTip="从外部打开文件",
        )

        self._analysis_tool_bar.addAction(self.action1_1)
        self.action1_1.setEnabled(False)

        icon2 = QIcon.fromTheme("document-send")
        self.action2 = QAction(
            icon2,
            "分析收支",
            self,
            triggered=self.analysis_income_expend_plot,
            statusTip="展示收支柱状图",
        )
        self._analysis_tool_bar.addAction(self.action2)
        self.action2.setEnabled(False)

        icon3 = QIcon.fromTheme("computer")
        self.action3 = QAction(
            icon3,
            "二维码收入",
            self,
            triggered=self.analysis_qrcode,
            statusTip="展示二维码收入折线图",
        )
        self._analysis_tool_bar.addAction(self.action3)
        self.action3.setEnabled(False)

        icon4 = QIcon.fromTheme("edit-find")
        self.action4 = QAction(
            icon4,
            "城市轨迹",
            self,
            triggered=self.analysis_cities,
            statusTip="统计客户消费城市轨迹饼图",
        )
        self._analysis_tool_bar.addAction(self.action4)
        self.action4.setEnabled(False)

        icon5 = QIcon.fromTheme("scanner")
        self.action5 = QAction(
            icon5,
            "活动时间",
            self,
            triggered=self.analysis_times,
            statusTip="展示客户交易时间分布图",
        )
        self._analysis_tool_bar.addAction(self.action5)
        self.action5.setEnabled(False)

        icon6 = QIcon.fromTheme("audio-card")
        self.action6 = QAction(
            icon6,
            "新增交易对象",
            self,
            triggered=self.analysis_abnormal,
            statusTip="搜集客户近期新增交易对象",
        )
        self._analysis_tool_bar.addAction(self.action6)
        self.action6.setEnabled(False)

        icon7 = QIcon.fromTheme("zoom-out")
        self.action7 = QAction(
            icon7,
            "刷新",
            self,
            triggered=self.refresh,
            statusTip="刷新之前加载的数据重新选择",
        )
        self._analysis_tool_bar.addAction(self.action7)

    @Slot(str)
    def get_path(self):
        """拿到pdf文件的硬盘路径  并传递给子线程方法后台处理"""
        pdf_filter = "Pdf files (*.pdf)"
        file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", pdf_filter)
        if file_path:
            self.start_thread()
            self.window_signal.emit(file_path)
        else:
            QMessageBox.warning(
                self, "warnings!", "please select file!", defaultButton=QMessageBox.Ok
            )

    @Slot(pd.DataFrame)
    def get_df(self, df: pd.DataFrame) -> pd.DataFrame:
        """receive dataframe file"""
        self.df = df
        self.action1_1.setEnabled(True)
        self.action2.setEnabled(True)
        self.action3.setEnabled(True)
        self.action4.setEnabled(True)
        self.action5.setEnabled(True)
        self.action6.setEnabled(True)

        self.stop_thread()
        return self.df

    # --------------------------显示透视表以及搜索框-----------------------------------------
    @Slot()
    def analysis_income_expend_tab(self):
        """显示交易对象的收支分析  按照收入列排序  并加入搜索框"""
        income = (
            self.df.query('`收/支/其他` == "收入"')
            .groupby("交易对方")
            .agg(收入金额=("金额(元)", "sum"), 收入次数=("金额(元)", "count"))
        )

        expend = (
            self.df.query('`收/支/其他` == "支出"')
            .groupby("交易对方")
            .agg(支出_金额=("金额(元)", "sum"), 支出_次数=("金额(元)", "count"))
        )

        tmp = income.merge(expend, how="outer", on="交易对方").sort_values(
            "收入金额", ascending=False
        )
        result = tmp.reset_index()
        result.fillna(0, inplace=True)

        # 创建QTableWidget实例
        self.table_widget = QTableWidget()
        self.table_widget.setRowCount(len(result))
        self.table_widget.setColumnCount(len(result.columns))
        # 设置表头
        self.table_widget.setHorizontalHeaderLabels(result.columns)
        # 遍历DataFrame填充到QTableWidget
        for row in range(len(result)):
            for col in range(len(result.columns)):
                item = QTableWidgetItem(str(result.iloc[row, col]))
                self.table_widget.setItem(row, col, item)
        self.table_widget.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch
        )
        self.table_widget.verticalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch
        )

        # 搜索框
        self.search_line = QLineEdit()
        self.search_line.textChanged.connect(self.set_color)
        self.search_btn = QPushButton("搜索关键字")
        self.search_btn.clicked.connect(self.search)
        self.search_line.returnPressed.connect(self.search)

        # 布局在一起
        tab_widget = QWidget()
        tab_layout = QVBoxLayout(tab_widget)
        tab_layout.addWidget(self.search_line)
        tab_layout.addWidget(self.search_btn)
        tab_layout.addWidget(self.table_widget)

        a = self._mdi_area.addSubWindow(tab_widget)
        a.setMinimumSize(500, 500)
        a.setWindowTitle("交易对象汇总表")
        a.show()

    @Slot()
    def search(self):
        """search key value and set color"""
        search_concent = self.search_line.text()
        if not search_concent:
            return
        else:
            self.res = self.table_widget.findItems(
                search_concent, Qt.MatchFlag.MatchContains
            )
            if len(self.res) > 0:
                self.table_widget.scrollToItem(
                    self.res[0], QTableWidget.ScrollHint.PositionAtTop
                )
            for item in self.res:
                item.setBackground(Qt.GlobalColor.darkGreen)

    @Slot(str)
    def set_color(self, text):
        """reset key value color"""
        if not text:
            for i in self.res:
                i.setBackground(Qt.GlobalColor.lightGray)

    # -----------------------------------------------------------------------------------------------

    # --------------------------------------------------画收支柱状图-----------------------------------------------

    def prepare_data(self):
        df = self.df.copy()
        df["交易时间"] = df["交易时间"].dt.strftime("%Y-%m")
        tmp = pd.DataFrame(
            df.groupby(["交易时间", "收/支/其他", "交易方式"])["金额(元)"].sum()
        )
        tmp = tmp.rename_axis(index=["日期", "收支类型", "交易方式"]).reset_index()
        self.bank = tmp.query("交易方式 == '银行'")
        self.wechat = tmp.query("交易方式 == '零钱'")
        return self.bank, self.wechat

    def plot_bank(self):
        bank = self.prepare_data()[0]
        bank_expend = bank[bank.收支类型 == "支出"]["金额(元)"].tolist()
        bank_income = bank[bank.收支类型 == "收入"]["金额(元)"].tolist()
        categories = bank[bank.收支类型 == "支出"]["日期"].tolist()

        low = QBarSet("支出")
        high = QBarSet("收入")
        low.append([-i for i in bank_expend])
        high.append(bank_income)

        series = QStackedBarSeries()
        series.append(low)
        series.append(high)

        chart = QChart()
        chart.addSeries(series)
        chart.setTitle("客户银行卡收支分布图")
        chart.setAnimationOptions(QChart.SeriesAnimations)

        axis_x = QBarCategoryAxis()
        axis_x.append(categories)
        axis_x.setTitleText("年月")
        chart.addAxis(axis_x, Qt.AlignBottom)
        axis_y = QValueAxis()
        axis_y.setRange(-max(bank_expend) - 500, max(bank_income) + 500)
        axis_y.setTitleText("金额")
        chart.addAxis(axis_y, Qt.AlignLeft)
        series.attachAxis(axis_x)
        series.attachAxis(axis_y)

        chart.legend().setVisible(True)
        chart.legend().setAlignment(Qt.AlignBottom)

        chart_view = QChartView(chart)
        chart_view.setRenderHint(QPainter.Antialiasing)
        return chart_view

    def plot_wechat(self):
        wechat = self.prepare_data()[1]
        wechat_expend = wechat[wechat.收支类型 == "支出"]["金额(元)"].tolist()
        wechat_income = wechat[wechat.收支类型 == "收入"]["金额(元)"].tolist()
        categories = wechat[wechat.收支类型 == "支出"]["日期"].tolist()

        low = QBarSet("支出")
        high = QBarSet("收入")
        low.append([-i for i in wechat_expend])
        high.append(wechat_income)

        series = QStackedBarSeries()
        series.append(low)
        series.append(high)

        chart = QChart()
        chart.addSeries(series)
        chart.setTitle("客户微信收支分布图")
        chart.setAnimationOptions(QChart.AnimationOption.SeriesAnimations)

        axis_x = QBarCategoryAxis()
        axis_x.append(categories)
        axis_x.setTitleText("年月")
        chart.addAxis(axis_x, Qt.AlignBottom)
        axis_y = QValueAxis()
        axis_y.setRange(-max(wechat_expend) - 500, max(wechat_income) + 500)
        axis_y.setTitleText("金额")
        chart.addAxis(axis_y, Qt.AlignLeft)
        series.attachAxis(axis_x)
        series.attachAxis(axis_y)

        chart.legend().setVisible(True)
        chart.legend().setAlignment(Qt.AlignBottom)

        chart_view = QChartView(chart)
        chart_view.setRenderHint(QPainter.Antialiasing)
        return chart_view

    @Slot()
    def analysis_income_expend_plot(self):
        plot_widget = QWidget()
        layout = QVBoxLayout(plot_widget)
        layout.addWidget(self.plot_bank())
        layout.addWidget(self.plot_wechat())

        b = self._mdi_area.addSubWindow(plot_widget)
        b.setMinimumSize(500, 500)
        b.setWindowTitle("客户每月收支总图")
        b.show()

    # -------------------------------------二维码功能-------------------------------------

    def get_qr_data(self):
        data = self.df[self.df["交易类型"].str.contains("收款")]
        tmpData = data.groupby(
            [
                data["交易时间"].dt.year,
                data["交易时间"].dt.month,
                data["交易时间"].dt.day,
            ]
        )["金额(元)"].sum()
        tmpData = tmpData.rename_axis(["年", "月", "日"], axis=0)

        lst1 = tmpData.index.get_level_values(0).tolist()
        lst2 = tmpData.index.get_level_values(1).tolist()
        res = list(set([(a, b) for a, b in zip(lst1, lst2)]))
        resData = []
        for i in res:
            tmpList = []
            j = tmpData.loc[i:i].reset_index()
            for k in j.iloc[:, 2:].values:
                value = QPointF(k[0], k[1])
                label = f"{i[0]}年{i[1]}月"
                tmpList.append((label, value))
            resData.append(tmpList)
        # print(resData)
        return resData

    @Slot()
    def analysis_qrcode(self):
        # print("分析二维码收支")

        chart = QChart()
        chart.setTitle("客户每个月二维码收入")
        chart.setAnimationOptions(QChart.AnimationOption.SeriesAnimations)

        resData = self.get_qr_data()
        x_lst = []
        y_lst = []

        for lst in resData:
            line_series = QLineSeries()
            for data in lst:
                line_series.append(data[1])
                x_lst.append(int(data[1].x()))
                y_lst.append(data[1].y())

            line_series.setName(data[0])

            
            chart.addSeries(line_series)


        chart.createDefaultAxes()
        axis_x = chart.axes(Qt.Horizontal)[0]
        axis_x.setRange(min(x_lst), max(x_lst)+1)
        axis_x.setTitleText("交易时间(天)")
    
        axis_y = chart.axes(Qt.Vertical)[0]
        axis_y.setRange(0, max(y_lst))
        axis_y.setTitleText("交易金额(元)")   
        axis_x.setLabelFormat('%d')
     

        chart_view = QChartView(chart)
        c = self._mdi_area.addSubWindow(chart_view)
        c.setWindowTitle("每年每个月二维码收支折线图")
        c.setMinimumSize(500, 500)
        c.show()

    # ------------------------------------------------------------------------------------------

    # __________________________________________城市轨迹____________________________________________
    def get_city_data(self):
        region = cpca.transform(self.df["交易对方"], index=self.df.index)
        provinces = region["省"].value_counts()
        provinces_v = [(a, b) for a, b in zip(provinces.index, provinces.values)]

        cities = region["市"].value_counts()
        cities_v = [(aa, bb) for aa, bb in zip(cities.index, cities.values)]

        return provinces_v, cities_v

    def get_city_view(self):
        ser1 = QPieSeries()
        ser2 = QPieSeries()
        data = self.get_city_data()
        for i in data[0]:
            ser1.append(i[0], i[1])
        ser1.setLabelsVisible(True)
        chart1 = QChart()
        chart1.addSeries(ser1)
        chart1.setTitle("省级分布图")

        for j in data[1]:
            ser2.append(j[0], j[1])
        ser2.setLabelsVisible(True)
        chart2 = QChart()
        chart2.addSeries(ser2)
        chart2.setTitle("市级分布图")

        chart_view1 = QChartView(chart1)
        chart_view2 = QChartView(chart2)
        return chart_view1, chart_view2

    @Slot()
    def analysis_cities(self):
        # print("分析城市轨迹（饼图）")

        chart_view1, chart_view2 = self.get_city_view()
        widget = QWidget()
        lay_out = QHBoxLayout(widget)
        lay_out.addWidget(chart_view1)
        lay_out.addWidget(chart_view2)

        d = self._mdi_area.addSubWindow(widget)
        d.setMinimumSize(500, 500)
        d.setWindowTitle("客户近期消费轨迹分布")
        d.show()

    # ---------------------------------------------------------------------------------------
    def get_hour_count(self):
        cnt = self.df["交易时间"].dt.hour.value_counts().sort_index()
        bar_set = QBarSet("交易数量")
        for v in cnt.values:
            bar_set.append(v)

        bar_series = QBarSeries()
        bar_series.append(bar_set)

        chart = QChart()
        chart.addSeries(bar_series)
        chart.setTitle("客户每天的交易时间分布图")
        chart.setAnimationOptions(QChart.SeriesAnimations)

        cat = [f"{ii}点" for ii in cnt.index.tolist()]
        axis_x = QBarCategoryAxis()
        axis_x.append(cat)
        axis_x.setTitleText("交易时间点")
        chart.addAxis(axis_x, Qt.AlignBottom)
        bar_series.attachAxis(axis_x)

        chart.legend().setVisible(True)
        chart_view = QChartView(chart)
        chart_view.setRenderHint(QPainter.Antialiasing)

        # 显示 QChartView
        return chart_view

    @Slot()
    def analysis_times(self):
        # print("交易时间分布")
        view = self.get_hour_count()
        f = self._mdi_area.addSubWindow(view)
        f.setWindowTitle("交易时间分布")
        f.setMinimumSize(500, 500)
        f.show()

    # --------------------------------------------------------------------------------------

    # __________________________新增交易对象___________________________________________

    def get_abnormal_obj(self):
        # tm = datetime(year=2024, month=5, day=9)
        tm = self.df.iloc[0, 0]
        tz = (tm - self.df["交易时间"]) / np.timedelta64(1, "D")
        self.df["时间间隔(天)"] = tz.astype(int)

        self.df["切分"] = pd.cut(
            self.df["时间间隔(天)"], bins=[-float("inf"), 7, 14, 30, float("inf")]
        ).astype("str")

        tmpList = []
        for i in self.df["切分"].unique():
            items = self.df[self.df.切分 == i]["交易对方"].tolist()
            tmpList.append(set(items))
        tmpList1 = list(((tmpList[0] - tmpList[1]) - tmpList[2]) - tmpList[3])

        return tmpList1

    @Slot()
    def analysis_abnormal(self):
        text = self.get_abnormal_obj()

        text_widget = QWidget()
        layout = QVBoxLayout(text_widget)
        text_edit = QTextEdit()
        text_edit.setText("近30天客户新增的交易对象")
        for t in text:
            text_edit.append(t)
        layout.addWidget(text_edit)

        g = self._mdi_area.addSubWindow(text_widget)
        g.setWindowTitle("新增交易对象")
        g.setMinimumSize(300, 500)
        g.show()

    @Slot()
    def refresh(self):
        """
        刷新按钮
        """
        self.action1_1.setEnabled(False)
        self.action2.setEnabled(False)
        self.action3.setEnabled(False)
        self.action4.setEnabled(False)
        self.action5.setEnabled(False)
        self.action6.setEnabled(False)

        self._progress_bar.setValue(0)  # 进度条设置为0
        self._mdi_area.closeAllSubWindows()  # 关闭所有子窗口
        # self.df = pd.DataFrame()  # 清空PDF文件
        del self.df

    def create_status_bar(self):
        """创建状态栏"""
        self.statusBar().showMessage("Ready")

    def read_settings(self):
        """
        恢复上次打开时候的页面大小
        """
        settings = QSettings("QtProject", "微信流水处理")
        geometry = settings.value("geometry", QByteArray())
        if geometry.size():
            self.restoreGeometry(geometry)

    def closeEvent(self, event):
        """这是一个QMainWindow类的方法重写,
        无需调用, 在点击右上角X的时候会自动触发"""
        if self.main_thread.isRunning():
            self.main_thread.quit()
            self.main_thread.wait()
        super().closeEvent(event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec())
